﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Xml;

namespace UPNP.IO
{
    partial class DeviceSchema
    {
        private void DownloadDeviceDescription()
        {
            try
            {
                if (_log.IsDebugEnabled)
                {
                    _log.DebugFormat(
                        "Downloading UPNP device description document for {0}: {1} ...",
                        this.UniqueDeviceName,
                        _rootDeviceSchemaUrl
                        );
                }

                // perform download
                var xd = DownloadAndParse(_rootDeviceSchemaUrl);

                // handle basic values
                var devElem = (XmlElement)xd.SelectSingleNode("/*[local-name()='root']/*[local-name()='device']");

                this.UniqueDeviceName = devElem.SelectSingleNodeInnerTextByLocalName("UDN", false);
                this.Type = devElem.SelectSingleNodeInnerTextByLocalName("deviceType", false);

                var modelName = devElem.SelectSingleNodeInnerTextByLocalName("modelName", false);
                var modelNumber = devElem.SelectSingleNodeInnerTextByLocalName("modelNumber", true);
                var manufacturer = devElem.SelectSingleNodeInnerTextByLocalName("manufacturer", false);

                this.SchemaId = DeviceHelpers.CreateId(
                    this.UniqueDeviceName,
                    modelName,
                    modelNumber,
                    manufacturer
                    );

                // update schema id
                _schemaIdAttribute.Value = this.SchemaId;

                // add device description
                {
                    var dd = _schema.CreateElement("deviceDescription");

                    dd.AppendChild(_schema.ImportNode(xd, true));

                    // add reference
                    {
                        var dRef = _schema.CreateAttribute("ref");

                        dRef.Value = ("#" + this.UniqueDeviceName);

                        dd.Attributes.Append(dRef);
                    }

                    // add url
                    {
                        var dRef = _schema.CreateAttribute("url");

                        dRef.Value = _rootDeviceSchemaUrl.ToString();

                        dd.Attributes.Append(dRef);
                    }

                    // add at last to prevent empty elements on exceptions
                    _root.AppendChild(dd);
                }

                // gather and download service descriptions
                {
                    var services = xd.OwnerDocument.SelectNodes("//*[local-name()='service']");

                    foreach (XmlElement s in services)
                    {
                        string sId = ("device " + this.UniqueDeviceName);

                        try
                        {
                            sId = s.SelectSingleNodeInnerTextByLocalName("serviceId", false);
                            var sUrl = new Uri(_rootDeviceSchemaUrl, s.SelectSingleNodeInnerTextByLocalName("SCPDURL", false));

                            // perform download
                            var sXd = DownloadAndParse(sUrl);

                            // add service description
                            {
                                var dd = _schema.CreateElement("serviceDescription");

                                dd.AppendChild(_schema.ImportNode(sXd, true));

                                // add reference
                                {
                                    var dRef = _schema.CreateAttribute("ref");

                                    dRef.Value = ("#" + sId);

                                    dd.Attributes.Append(dRef);
                                }

                                // add url
                                {
                                    var dRef = _schema.CreateAttribute("url");

                                    dRef.Value = sUrl.ToString();

                                    dd.Attributes.Append(dRef);
                                }

                                // add at last to prevent empty elements on exceptions
                                _root.AppendChild(dd);
                            }
                        }
                        catch (Exception e)
                        {
                            _log.Error("Failed to download UPNP service description document for " + sId, e);

                            // add comment on failed download
                            _root.AppendChild(_schema.CreateComment("Failed to download UPNP service description document for " + sId));
                        }
                    }
                }
            }
            catch (Exception e)
            {
                _log.Error("Failed to download UPNP device description document for " + this.UniqueDeviceName, e);

                throw;
            }
        }

        private XmlElement DownloadAndParse(Uri url)
        {

            if (_log.IsDebugEnabled)
            {
                _log.DebugFormat(
                    "Downloading any description document for {0}: {1} ...",
                    this.UniqueDeviceName,
                    url
                    );
            }
            try
            {
                using (var wc = new WebClient())
                {
                    using (var ds = wc.OpenRead(url))
                    {
                        var xd = new XmlDocument();

                        xd.Load(ds);

                        // gather first root element
                        foreach (var cn in xd.ChildNodes)
                        {
                            var cne = cn as XmlElement;

                            if (cne != null)
                                return cne;
                        }

                        // no root element found
                        throw new NotSupportedException("no root element found");
                    }
                }
            }
            catch (Exception e)
            {
                _log.Error("Failed to download any description document for " + this.UniqueDeviceName + ": " + url.ToString(), e);

                throw;
            }
        }
    }
}
